package main

import (
	"bufio"
	"errors"
	"fmt"
	"github.com/go-yaml/yaml"
	"io/ioutil"
	"log"
	"os"
	"regexp"
	"strconv"
	"strings"
)

func failOnError(err error, msg string) {
	if err != nil {
		log.Fatalf("%s: %s", msg, err)
	}
}

type ConfigExtraThread struct {
	PID string `yaml:"pid"`
	Title string `yaml:"title"`
}

func NewConfigExtraThread() *ConfigExtraThread {
	return &ConfigExtraThread{}
}

type Config struct {
	LogFile string `yaml:"logFile"`
	OutputFile string `yaml:"outputFile"`
	OutputType int `yaml:"outputType"`
	SliExtraThread []*ConfigExtraThread `yaml:"sliExtraThread"`
}

func NewConfig() *Config {
	return &Config{}
}

func (this *Config) load(filename string) {
	f, e := ioutil.ReadFile(filename)
	failOnError(e, "ReadFile")

	e = yaml.Unmarshal(f, this)
	failOnError(e, "Unmarshal")
}

type LogThread struct {
	pID string
	cpuPer string
	memPer string
	memVirt string
	memRes string
	memShr string
}

func NewLogThread() *LogThread {
	return &LogThread{}
}

type LogProcess struct {
	process   string
	ptrMain   *LogThread
}

func NewLogProcess() *LogProcess {
	return &LogProcess{}
}

func (this *LogProcess) getMemPer() string {
	return this.getMemPerMain() // 这里的内存占用实际不是格个线程的内存和(先取主线程的内存占用了)
}

func (this *LogProcess) getCpuPerMain() string {
	if this.ptrMain == nil {
		return "-1"
		//failOnError(errors.New("this.ptrMain == nil"), "getCpuPerMain")
	}
	return this.ptrMain.cpuPer
}

func (this *LogProcess) getMemPerMain() string {
	if this.ptrMain == nil {
		return "-1"
		//failOnError(errors.New("this.ptrMain == nil"), "getMemPerMain")
	}
	return this.ptrMain.memPer
}

func (this *LogProcess) getMemVirtMain() string {
	if this.ptrMain == nil {
		failOnError(errors.New("this.ptrMain == nil"), "getMemVirtMain")
	}
	return this.ptrMain.memVirt
}

func (this *LogProcess) getMemResMain() string {
	if this.ptrMain == nil {
		failOnError(errors.New("this.ptrMain == nil"), "getMemResMain")
	}
	return this.ptrMain.memRes
}

func (this *LogProcess) getMemShrMain() string {
	if this.ptrMain == nil {
		failOnError(errors.New("this.ptrMain == nil"), "getMemShrMain")
	}
	return this.ptrMain.memShr
}

type LogBase struct {
	load string
	cpuUsed string
	cpuIdle string
	memTotal string
	memFree string
	memUsed string
	memCache string
}

func NewLogBase() *LogBase {
	return &LogBase{}
}

type LogLine struct {
	curTime string
	ptrBase *LogBase
	ptrPhoenix *LogProcess
	ptrGate *LogProcess
	ptrWorld *LogProcess
	mapThread map[string]*LogThread
}

func NewLogLine() *LogLine {
	return &LogLine{
		ptrBase: NewLogBase(),
		ptrPhoenix: NewLogProcess(),
		ptrGate: NewLogProcess(),
		ptrWorld: NewLogProcess(),
		mapThread: make(map[string]*LogThread),
	}
}

func (this *LogLine) getLogThread(pID string) *LogThread {
	return this.mapThread[pID]
}

type Analyzer struct {
	mapPID map[string]bool
	sliLogLine []*LogLine
}

func NewAnalyzer() *Analyzer {
	return &Analyzer{
		mapPID:make(map[string]bool),
	}
}

func (this *Analyzer) inPID(pid string) bool {
	_, ok := this.mapPID[pid]
	return ok
}

func (this *Analyzer) find(l string, key string) string {
	i := strings.Index(l, key)
	if i < 0 {
		failOnError(errors.New("i < 0"), "Index")
	}
	j := strings.LastIndex(l[:i], ",")
	if j < 0 {
		j = strings.LastIndex(l[:i], ":")
	}
	if j < 0 {
		failOnError(errors.New("j < 0"), "Index")
	}
	return strings.Trim(l[j+1:i], " ")
}

// 有单位的数字转换
func (this *Analyzer) convert(src string) string {
	if _, e := strconv.ParseFloat(src, 64); e == nil {
		return src
	}
	i := src[:len(src)-1]
	s := src[len(src)-1:]
	m := float64(1)
	switch s {
	case "k":
		m = 1024
	case "m":
		m = 1024*1024
	case "g":
		m = 1024*1024*1024
	}
	f, e := strconv.ParseFloat(i, 64)
	failOnError(e, "ParseFloat")
	f *= m

	return fmt.Sprintf("%v", f)
}

func (this *Analyzer) analyze(ptrCfg *Config) {
	log.Println("开始分析文件")
	if ptrCfg == nil {
		failOnError(errors.New("ptrCfg == nil"), "analyze")
	}

	f, e := os.Open(ptrCfg.LogFile)
	failOnError(e, "Open")
	defer f.Close()

	reg, e := regexp.Compile("[ ]+")
	failOnError(e, "Compile")

	r := bufio.NewReader(f)
	index := 0
	ptrLogLine := NewLogLine()

	// 头数据
	l, e := r.ReadString('\n')
	i := strings.Index(l, ":")
	if i < 0 {
		failOnError(errors.New("i < 0"), "Index")
	}
	strPID := l[i+1:len(l)-1]
	sliPID := strings.Split(strPID, ",")
	for _, pid := range sliPID {
		this.mapPID[pid] = true
	}
	r.ReadString('\n') // 读一个空行

	// 循环解析
	for {
		l, e := r.ReadString('\n')
		if e != nil {
			break
		}
		index++
		if strings.Index(l, "====") >= 0 {
			l, e = r.ReadString('\n') // 过掉一个空行
			index = 0
		}
		if index == 0 {
			this.sliLogLine = append(this.sliLogLine, ptrLogLine)
			ptrLogLine = NewLogLine()
		}

		//log.Println(l)
		switch {
		case index == 1: // 时间
			{
				ptrLogLine.curTime = strings.Trim(l, "\n")
			}
		case index == 3: // 负载
			{
				key := "load average:"
				if i := strings.Index(l, key); i >= 0 {
					ptrLogLine.ptrBase.load = strings.Trim(l[i+len(key)+1:], "\n")
				} else {
					failOnError(errors.New("i < 0"), "Index")
				}
			}
		case index == 5: // CPU
			{
				ptrLogLine.ptrBase.cpuUsed = this.find(l,"us")
				ptrLogLine.ptrBase.cpuIdle = this.find(l,"id")
			}
		case index == 6: // 内存
		{
			ptrLogLine.ptrBase.memTotal = this.find(l,"total")
			ptrLogLine.ptrBase.memFree = this.find(l,"free")
			ptrLogLine.ptrBase.memUsed = this.find(l,"used")
			ptrLogLine.ptrBase.memCache = this.find(l,"buff")
		}
		case index >= 10: // 进程数据
		{
			l = reg.ReplaceAllString(l, " ")
			l = strings.Trim(l, " ")
			sliData := strings.Split(l, " ")
			length := len(sliData)
			if length != 12 {
				failOnError(errors.New("length != 12"), "len")
			}
			process := sliData[11]
			process = strings.Trim(process, "\n")
			var ptrProcess *LogProcess = nil
			switch process {
			case "PhoenixA":
				{
					ptrProcess = ptrLogLine.ptrPhoenix
				}
			case "GateServerA":
				{
					ptrProcess = ptrLogLine.ptrGate
				}
			case "WorldServerA":
				{
					ptrProcess = ptrLogLine.ptrWorld
				}
			}
			if ptrProcess == nil {
				failOnError(errors.New("ptrProcess == nil"), "switch")
			}
			ptrProcess.process = process
			ptrThread := NewLogThread()
			ptrThread.pID = sliData[0]
			ptrThread.cpuPer = sliData[8]
			ptrThread.memPer = sliData[9]
			ptrThread.memVirt = this.convert(sliData[4])
			ptrThread.memRes = this.convert(sliData[5])
			ptrThread.memShr = this.convert(sliData[6])
			if this.inPID(ptrThread.pID) { // 主线程处理
				ptrProcess.ptrMain = ptrThread
			}
			ptrLogLine.mapThread[ptrThread.pID] = ptrThread
		}
		default:
			{

			}
		}
	}
}

func (this *Analyzer) write(ptrCfg *Config) {
	log.Println("开始输出分析文件")
	if ptrCfg == nil {
		failOnError(errors.New("ptrCfg == nil"), "write")
	}

	f, e := os.Create(ptrCfg.OutputFile)
	failOnError(e, "Open")
	defer f.Close()

	switch ptrCfg.OutputType {
	case 0:
		{
			f.WriteString("已失效\n")
			//f.WriteString("时间, 网关CPU%, 网关内存%, 世界CPU%, 世界内存%, 逻辑CPU%, 逻辑内存%\n")
			//
			//for _, ptrLogLine := range this.sliLogLine {
			//	f.WriteString(fmt.Sprintf("%v, %v, %v, %v, %v, %v, %v\n", ptrLogLine.curTime, ptrLogLine.ptrGate.getCpuPer(), ptrLogLine.ptrGate.getMemPer(), ptrLogLine.ptrWorld.getCpuPer(), ptrLogLine.ptrWorld.getMemPer(), ptrLogLine.ptrPhoenix.getCpuPer, ptrLogLine.ptrPhoenix.getMemPer()))
			//}
		}
	case 1:
		{
			f.WriteString("已失效\n")
			//f.WriteString("索引, 时间, 负载1, 负载2, 负载3, CPU使用, CPU空闲, 总内存, 空闲内存, 使用内存, Cache内存, ")
			//f.WriteString("进程名, CPU%, 内存%, VIRT, RES, SHR, ")
			//f.WriteString("进程名, CPU%, 内存%, VIRT, RES, SHR, ")
			//f.WriteString("进程名, CPU%, 内存%, VIRT, RES, SHR\n")
			//
			//for index, ptrLogLine := range this.sliLogLine {
			//	f.WriteString(fmt.Sprintf("%v, %v, %v, %v, %v, %v, %v, %v, %v, ", index, ptrLogLine.curTime, ptrLogLine.ptrBase.load, ptrLogLine.ptrBase.cpuUsed, ptrLogLine.ptrBase.cpuIdle, ptrLogLine.ptrBase.memTotal, ptrLogLine.ptrBase.memFree, ptrLogLine.ptrBase.memUsed, ptrLogLine.ptrBase.memCache))
			//	f.WriteString(fmt.Sprintf("%v, %v, %v, %v, %v, %v, ", ptrLogLine.ptrGate.process, ptrLogLine.ptrGate.getCpuPer(), ptrLogLine.ptrGate.getMemPer(), ptrLogLine.ptrGate.getMemVirt(), ptrLogLine.ptrGate.getMemRes(), ptrLogLine.ptrGate.getMemShr()))
			//	f.WriteString(fmt.Sprintf("%v, %v, %v, %v, %v, %v, ", ptrLogLine.ptrWorld.process, ptrLogLine.ptrWorld.getCpuPer(), ptrLogLine.ptrWorld.getMemPer(), ptrLogLine.ptrWorld.getMemVirt(), ptrLogLine.ptrWorld.getMemRes(), ptrLogLine.ptrWorld.getMemShr()))
			//	f.WriteString(fmt.Sprintf("%v, %v, %v, %v, %v, %v\n", ptrLogLine.ptrPhoenix.process, ptrLogLine.ptrPhoenix.getCpuPer(), ptrLogLine.ptrPhoenix.getMemPer(), ptrLogLine.ptrPhoenix.getMemVirt(), ptrLogLine.ptrPhoenix.getMemRes(), ptrLogLine.ptrPhoenix.getMemShr()))
			//}
		}
	case 2:
		{
			f.WriteString("时间, 网关主线程CPU%, 网关内存%, 世界主线程CPU%, 世界内存%, 主线程逻辑CPU%, 主线程逻辑内存%")

			// 补齐额外表头
			strTitle := ",";
			for _, ptrExtraThread := range ptrCfg.SliExtraThread {
				strTitle += ptrExtraThread.Title + ","
			}
			strTitle = strTitle[:len(strTitle)-1]
			strTitle += "\n"
			f.WriteString(strTitle)

			for _, ptrLogLine := range this.sliLogLine {
				f.WriteString(fmt.Sprintf("%v, %v, %v, %v, %v, %v, %v", ptrLogLine.curTime, ptrLogLine.ptrGate.getCpuPerMain(), ptrLogLine.ptrGate.getMemPer(), ptrLogLine.ptrWorld.getCpuPerMain(), ptrLogLine.ptrWorld.getMemPer(), ptrLogLine.ptrPhoenix.getCpuPerMain(), ptrLogLine.ptrPhoenix.getMemPerMain()))

				strExtra := ","
				for index, ptrExtraThread := range ptrCfg.SliExtraThread {
					if ptrLogThread := ptrLogLine.getLogThread(ptrExtraThread.PID); ptrLogThread != nil {
						strExtra += ptrLogThread.cpuPer + ","
					} else {
						strExtra += "0" + ","
						log.Printf("index:%v,pid:%v,logThread not found", index, ptrExtraThread.PID)
					}
				}
				strExtra = strExtra[:len(strExtra)-1]
				strExtra += "\n"
				f.WriteString(strExtra)
			}
		}
	default:
		failOnError(errors.New(fmt.Sprintf("无效的ptrCfg.OutputType:%v", ptrCfg.OutputType)), "write")
	}
}

func main() {
	// 读取配置
	ptrCfg := NewConfig()
	ptrCfg.load("analyzer.yaml")
	log.Printf("开始日志分析,日志文件:%v,输出文件:%v", ptrCfg.LogFile, ptrCfg.OutputFile)

	ptrAnalyzer := NewAnalyzer()
	ptrAnalyzer.analyze(ptrCfg)
	ptrAnalyzer.write(ptrCfg)
}
